package validation

import (
	"context"
	"fmt"
	"github.com/stretchr/testify/assert"
	"gitverse.ru/synapse/kubelatte/pkg/api/common"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	controllers "gitverse.ru/synapse/kubelatte/pkg/operator"
	"gitverse.ru/synapse/kubelatte/pkg/storage"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"os"
	"testing"
	"time"
)

func TestMain(m *testing.M) {
	// test context initialization here
	logs.Logger = zap.NewNop()
	os.Exit(m.Run())
}

func TestIteratorNewLogic(t *testing.T) {
	type args struct {
		elem      interface{}
		rule      v1alpha1.Simple
		isReverse bool
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "allowed-slice",
			args: args{
				elem: []string{"annot1", "annot2"},
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "(annot1|annot2|annot3)",
				},
			},
			want: true,
		},
		{
			name: "allowed-slice-reversed",
			args: args{
				elem: []string{"annot1", "annot2"},
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "(annot1|annot2|annot3)",
				},
				isReverse: true,
			},
			want: true,
		},
		{
			name: "disallowed-slice",
			args: args{
				elem: []string{"annot", "annot2"},
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "(annot1|annot2|annot3)",
				},
			},
			want: false,
		},
		{
			name: "allowed-string",
			args: args{
				elem: "annot1",
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "(annot1|annot2|annot3)",
				},
			},
			want: true,
		},
		{
			name: "allowed-string-reversed",
			args: args{
				elem: "annot1",
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "(annot1|annot2|annot3)",
				},
				isReverse: true,
			},
			want: true,
		},
		{
			name: "disallowed-string",
			args: args{
				elem: "not-annot",
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "(annot1|annot2|annot3)",
				},
			},
			want: false,
		},
		{
			name: "invalid-regexp-string",
			args: args{
				elem: "not-annot",
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "ffff",
				},
			},
			want: false,
		},
		{
			name: "match-regexp-error-string",
			args: args{
				elem: "not-annot",
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "a(b",
				},
			},
			want: false,
		},
		{
			name: "disallowed-type",
			args: args{
				elem: true,
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "a(b",
				},
			},
			want: false,
		},
		{
			name: "allowed-map",
			args: args{
				elem: map[string]string{"annot1": "ONE", "annot2": "TWO"},
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: ".*",
				},
			},
			want: true,
		},
		{
			name: "allowed-map-reversed",
			args: args{
				elem: map[string]string{"annot1": "ONE", "annot2": "TWO"},
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: ".*",
				},
				isReverse: true,
			},
			want: true,
		},
		{
			name: "disallowed-map",
			args: args{
				elem: map[string]string{"annot1": "ONE", "annot2": "TWO"},
				rule: v1alpha1.Simple{
					Name:  "allowed-volumes-annotations-name",
					Path:  "metadata.annotations|keys(@)",
					Value: "^$",
				},
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Iterator(context.Background(), tt.args.elem, tt.args.rule.Path, tt.args.rule.Value, tt.args.isReverse); got != tt.want {
				t.Errorf("IteratorNewLogic() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_checkScopeNewLogic(t *testing.T) {
	type args struct {
		result interface{}
		simple v1alpha1.Simple
		action string
	}
	tests := []struct {
		name   string
		args   args
		result bool
	}{
		{
			name: ".* 1",
			args: args{
				result: []string{"annot", "key"},
				simple: v1alpha1.Simple{
					Name:  "disallowed-containers",
					Path:  "spec.containers",
					Value: ".*",
				},
				action: Deny,
			},
			result: false,
		},
		{
			name: ".* 2",
			args: args{
				result: []string{"annot", "key"},
				simple: v1alpha1.Simple{
					Name:  "disallowed-containers",
					Path:  "spec.containers",
					Value: ".*",
				},
				action: Allow,
			},
			result: true,
		},
		{
			name: "empty res",
			args: args{
				result: nil,
				simple: v1alpha1.Simple{
					Name:  "disallowed-containers",
					Path:  "spec.containers",
					Value: ".*",
				},
				action: Deny,
			},
			result: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equalf(t, tt.result, validateValue(context.Background(), tt.args.result, tt.args.simple.Path, tt.args.simple.Value, tt.args.action),
				"validateValue(%v, %v, %v)", tt.args.result, tt.args.simple, tt.args.action)
		})
	}
}

func TestCheckReverseScopeWithRuleField(t *testing.T) {
	type args struct {
		original    map[string]interface{}
		requestData common.ARFields
	}
	scope := v1alpha1.Scope{
		TypeMeta: metav1.TypeMeta{
			Kind:       "",
			APIVersion: "",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test",
			Namespace: "test",
		},
		Spec: v1alpha1.ScopeSpec{
			Type:  "validation",
			Items: []v1alpha1.Item{},
		},
		Status: v1alpha1.ScopeStatus{},
	}
	storageController := &storage.StorageController{Sync: false}
	storage.Storage = storageController
	storageController.Start(false, false)

	tests := []struct {
		name    string
		args    args
		wantErr assert.ErrorAssertionFunc
	}{
		{
			name: "denied containers in AR",
			args: args{
				requestData: common.ARFields{
					Kind: metav1.GroupVersionKind{
						Group:   "",
						Version: "v1",
						Kind:    "Pod",
					},
					Namespace: "",
					UserInfo:  common.ARUserInfo{},
					Operation: "CREATE",
					OldObject: nil,
					Object: map[string]interface{}{
						"metadata": map[string]interface{}{
							"annotation": map[string]interface{}{
								"annot1": "enabled",
								"annot2": "enabled",
							},
						},
						"spec": map[string]interface{}{
							"containers": []map[string]interface{}{{
								"name":  "some container",
								"image": "image",
							},
							},
						},
					},
				},
			},
			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
				assert.True(t, err != nil)
				return err != nil
			},
		},
		{
			name: "allowed containers in AR",
			args: args{
				requestData: common.ARFields{
					Kind: metav1.GroupVersionKind{
						Group:   "",
						Version: "v1",
						Kind:    "Pod",
					},
					Namespace: "",
					UserInfo:  common.ARUserInfo{},
					Operation: "CREATE",
					OldObject: nil,
					Object: map[string]interface{}{
						"metadata": map[string]interface{}{
							"annotation": map[string]interface{}{
								"annot1": "enabled",
								"annot2": "enabled",
							},
						},
						"spec": map[string]interface{}{
							"containers": []map[string]interface{}{{
								"name":  "some container",
								"image": "image",
							},
							},
						},
					},
				},
			},
			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
				assert.True(t, err == nil)
				return err == nil
			},
		},
		{
			name: "allowed containers not in AR",
			args: args{
				requestData: common.ARFields{
					Kind: metav1.GroupVersionKind{
						Group:   "",
						Version: "v1",
						Kind:    "Pod",
					},
					Namespace: "",
					UserInfo:  common.ARUserInfo{},
					Operation: "CREATE",
					OldObject: nil,
					Object: map[string]interface{}{
						"metadata": map[string]interface{}{
							"annotation": map[string]interface{}{
								"annot1": "enabled",
								"annot2": "enabled",
							},
						},
						"spec": map[string]interface{}{
							"containers": []map[string]interface{}{{
								"name":  "some another container",
								"image": "image",
							},
							},
						},
					},
				},
			},
			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
				assert.True(t, err != nil)
				return err != nil
			},
		},
		{
			name: "denied volumes",
			args: args{
				requestData: common.ARFields{
					Kind: metav1.GroupVersionKind{
						Group:   "",
						Version: "v1",
						Kind:    "Pod",
					},
					Namespace: "",
					UserInfo:  common.ARUserInfo{},
					Operation: "CREATE",
					OldObject: nil,
					Object: map[string]interface{}{
						"metadata": map[string]interface{}{
							"annotation": map[string]interface{}{
								"annot1": "enabled",
								"annot2": "enabled",
							},
						},
						"spec": map[string]interface{}{
							"volumes": map[string]interface{}{
								"vol": "lolol",
							},
						},
					},
				},
			},
			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
				assert.True(t, err != nil)
				return err != nil
			},
		},
		{
			name: "denied volumes",
			args: args{
				requestData: common.ARFields{
					Kind: metav1.GroupVersionKind{
						Group:   "",
						Version: "v1",
						Kind:    "Pod",
					},
					Namespace: "",
					UserInfo:  common.ARUserInfo{},
					Operation: "CREATE",
					OldObject: nil,
					Object: map[string]interface{}{
						"metadata": map[string]interface{}{
							"annotation": map[string]interface{}{
								"annot1": "enabled",
								"annot2": "enabled",
							},
						},
						"spec": map[string]interface{}{
							"volumes": map[string]interface{}{
								"vol": "lolol",
							},
						},
					},
				},
			},
			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
				assert.True(t, err != nil)
				return err != nil
			},
		},
	}

	for i, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			switch i {
			case 0:
				scope.Spec.Items = []v1alpha1.Item{
					{
						Name:  "container-name",
						Match: v1alpha1.Match{},
						Rule: v1alpha1.Rule{Simples: []v1alpha1.Simple{{
							Name:    "container-name",
							Path:    "spec.containers[].name",
							Value:   "some container",
							Action:  Deny,
							Message: "lalala lululu",
						}}},
					},
				}
				util.SendEvent(controllers.StorageUpdateScopeEvent, scope)
				time.Sleep(1 * time.Second)
			case 1, 2:
				util.SendEvent(controllers.StorageDeleteScopeEvent, scope)
				time.Sleep(1 * time.Second)
				scope.Spec.Items = []v1alpha1.Item{
					{
						Name:  "container-name",
						Match: v1alpha1.Match{},
						Rule: v1alpha1.Rule{Simples: []v1alpha1.Simple{{
							Name:    "container-name",
							Path:    "spec.containers[].name",
							Value:   "some container",
							Action:  Allow,
							Message: "lalala lululu",
						}}},
					},
				}
				util.SendEvent(controllers.StorageUpdateScopeEvent, scope)
				time.Sleep(1 * time.Second)
			case 3, 4:
				util.SendEvent(controllers.StorageDeleteScopeEvent, scope)
				time.Sleep(1 * time.Second)
				scope.Spec.Items = []v1alpha1.Item{
					{
						Name: "disallowed-volumes",
						Rule: v1alpha1.Rule{Simples: []v1alpha1.Simple{{
							Name:    "disallowed-volumes",
							Path:    "spec.volumes",
							Value:   ".*",
							Action:  Deny,
							Message: "",
						}}},
					},
				}
				util.SendEvent(controllers.StorageUpdateScopeEvent, scope)
				time.Sleep(1 * time.Second)
			}

			err := ValidateResource(context.Background(), tt.args.original, tt.args.requestData)
			tt.wantErr(t, err, fmt.Sprintf("ValidateResource(%v, %v)", tt.args.original, tt.args.requestData))

		})
	}
}

func TestCheckRegoRule(t *testing.T) {
	t.Run("with rule", func(t *testing.T) {
		storage.Storage = &storage.StorageController{}
		storage.Storage.Start(true, false)
		orig := map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "Pod",
			"metadata": map[string]interface{}{
				"name":      "test",
				"namespace": "test"},
		}
		item := v1alpha1.Item{
			Name: "rule",
			Match: v1alpha1.Match{
				Kinds: []v1alpha1.Kind{{
					Kind:      []string{"Pod"},
					ApiGroups: []string{"*"},
				}},
			},
			Rule: v1alpha1.Rule{
				Rego: v1alpha1.Rego{
					Template:   "import future.keywords.contains\nimport future.keywords.if\nimport future.keywords.in\n\nviolation contains {\"msg\": msg} if {\n  params := object.get(input, \"parameters\", {})\n  name := object.get(params, \"name\", [])\n  objName := input.review.name\n  objName == name\n  msg := sprintf(\"Creating this pod is prohibited! Do not create %v\", [objName])\n}",
					Parameters: "name: test",
				},
			},
		}
		if gotErr := CheckRegoRule(context.Background(), orig, item); gotErr == nil {
			t.Errorf("CheckRegoRule() error: %v, expected nil", gotErr)
		}
		storage.Storage.UpdateRegoRule("test", v1alpha1.TemplateSpec{
			Kind:       "Scope",
			ApiVersion: "test",
			Data:       "import future.keywords.contains\nimport future.keywords.if\nimport future.keywords.in\n\nviolation contains {\"msg\": msg} if {\n  params := object.get(input, \"parameters\", {})\n  name := object.get(params, \"name\", [])\n  objName := input.review.name\n  objName == name\n  msg := sprintf(\"Creating this pod is prohibited! Do not create %v\", [objName])\n}",
			Type:       "rego",
		})
		if gotErr := CheckRegoRule(context.Background(), orig, item); gotErr == nil {
			t.Errorf("CheckRegoRule() error: %v, expected nil", gotErr)
		}
	})
}
